Skip to content

SolveRoot with local Optimization #11429

Open
amirroth wants to merge 13 commits intodevelopfrom
SolveRoot
Open

SolveRoot with local Optimization #11429
amirroth wants to merge 13 commits intodevelopfrom
SolveRoot

Conversation

@amirroth
Copy link
Collaborator

@amirroth amirroth commented Feb 23, 2026

Pull request overview

This PR introduces a wrapper to SolveRoot that attempts to select the optimal algortihm (RegulaFalsi, Bisection, SomeCombinationA, AnotherCombinationB) on a call site basis.

It introduces function SolveRoot2 which takes an additional solveRootStats structure reference argument. It uses this argument to run a different candidate algorithm during each call. After every candidate algorithm has run five times, the winning algorithm (i.e., the one that resulted in the fewest total iterations) is used for the rest of the run. There is a new unit test that demonstrates how the function is supposed to work.

The SolveRootStats structure should be part of state to allow it to be shared across calls. It could be part of module state if all objects of the same type should share a single algorithm. It should be part of object state if each object is optimized independently.

There are of course a number of small diffs in the regression suite, and these are due to the fact that SolveRoot may be using a different algorithm and is therefore coming up with slightly different roots. Most of the diffs are "small". But even the "big" diffs are relatively small. Is a temperature difference of 0.005 C "big"? Is a power difference of 0.1 W "big"? Right now we have a fixed definition where an absolute difference of 0.001 or a relative difference of 0.005 is considered big. But maybe we need to rethink things. Maybe we need to ratchet up the absolute difference globally, or maybe selectively (this would be more complicated).

Right now, this new function is only used in five modules (SingleDuct, WaterThermalTanks, and several others), and never in sizing routines, which presumably only run once. I will slowly propagate it in future PRs. Others feel free to do the same.

@amirroth amirroth self-assigned this Feb 23, 2026
@amirroth amirroth added the DoNotPublish Includes changes that shouldn't be reported in the changelog label Feb 23, 2026
@amirroth amirroth added this to the EnergyPlus 26.1 milestone Feb 23, 2026
Copy link
Collaborator Author

@amirroth amirroth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Short code review, not much to this PR.

this->dataErrorsFound = true;
} else if (SolFla == -2) {

} else if (SolFla == General::SOLVEROOT_ERROR_INIT) {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added these constexpr definitions as well since nobody can be expected to remember what -1 and -2 stand for.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think I'll ever forget

@@ -0,0 +1,71 @@
// EnergyPlus, Copyright (c) 1996-2024, The Board of Trustees of the University of Illinois,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know what this is doing here.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should these files be removed?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like these two files may have been added in inadvertently. I went ahead and removed them.

XRes = XTemp;
}

// A second version that does not require a payload -- use lambdas
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the new function.

RootAlgo algoTemp = state.dataRootFinder->rootAlgo;
state.dataRootFinder->rootAlgo = stats.algo;

SolveRoot(state, Eps, maxIters, SolFla, XRes, f, X_0, X_1);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At some point, the algortihm should just be made a parameter to this call, but that would have resulted in 200+ diffs.

};
struct RootFindingData : BaseGlobalStruct
{
HVACSystemRootFindingAlgorithm HVACSystemRootFinding;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need for this additional object when RootFindingData is already an enclosing object.

auto f = [&state, H, RH, PB](Real64 const Tprov) { return H - Psychrometrics::PsyHFnTdbRhPb(state, Tprov, RH, PB); };

General::SolveRoot(state, Acc, MaxIte, SolFla, Tprov, f, T0, T1);
static General::SolveRootStats solveRootStats;
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not thread safe, but I didn't now where to put it. Someplace in dataPsychrometrics I guess?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe here:

struct GeneralData : BaseGlobalStruct

@@ -5,12 +5,23 @@
# Regarding POWER/PowerPC, just as is noted in the Qt source,
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why this is here. Probably needs a separate PR if someone hasn't already addressed this.

EXPECT_NEAR(state->dataLoopNodes->Node(thisFanCoil.ATMixerPriNode).MassFlowRate, 0.1, 0.000001);
EXPECT_NEAR(state->dataLoopNodes->Node(thisFanCoil.ATMixerSecNode).MassFlowRate, 0.035129, 0.000001);
EXPECT_NEAR(state->dataLoopNodes->Node(thisFanCoil.ATMixerOutNode).MassFlowRate, 0.135129, 0.000001);
EXPECT_NEAR(state->dataLoopNodes->Node(thisFanCoil.ATMixerSecNode).MassFlowRate, 0.035129, 0.00003);
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since we are changing root finding algorithm, answers may differ slightly. There is no reason why tolerances should be 0.0001%. Nothing in BEM is known to this level of precision.

}

TEST_F(EnergyPlusFixture, General_SolveRoot2)
{
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New unit test to make sure SolveRoot2 works as expected. Use two kinds of functions.

EXPECT_TRUE(WaterThermalTanks::TankOperatingMode::Floating == HeatPump.Mode); // expect HP to switch to floating mode since it reached set point
EXPECT_NEAR(58.0005664, Tank.TankTempAvg, 0.0000001); // average tank temp over time step
EXPECT_NEAR(58.0005664, Tank.SourceOutletTemp, 0.0000001); // source outlet = average tank temp
EXPECT_NEAR(58.0005664, Tank.TankTempAvg, 0.001); // Was 0.0000001 !?! // average tank temp over time step
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do temperatures need tolerances of 0.0000001 C? Isn't 0.001 C enough?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our temperature "sensors" are perfect, so yes ;)

@github-actions
Copy link

⚠️ Regressions detected on macos-14 for commit a1fbf48

Regression Summary
  • ERR: 4
  • Table Small Diffs: 9
  • EIO: 10
  • ESO Small Diffs: 16
  • MTR Small Diffs: 14
  • Table Big Diffs: 4
  • Table String Diffs: 7
  • ESO Big Diffs: 4

@github-actions
Copy link

⚠️ Regressions detected on ubuntu-24.04 for commit a1fbf48

Regression Summary
  • ERR: 4
  • Table Small Diffs: 7
  • EIO: 11
  • ESO Small Diffs: 16
  • MTR Small Diffs: 13
  • Table Big Diffs: 4
  • Table String Diffs: 7
  • ESO Big Diffs: 4
  • MTR Big Diffs: 1

@github-actions
Copy link

⚠️ Regressions detected on ubuntu-24.04 for commit 67f212e

Regression Summary
  • ERR: 5
  • Table Small Diffs: 12
  • EIO: 11
  • ESO Small Diffs: 15
  • MTR Small Diffs: 14
  • Table Big Diffs: 2
  • Table String Diffs: 7
  • ESO Big Diffs: 5

@github-actions
Copy link

⚠️ Regressions detected on macos-14 for commit 67f212e

Regression Summary
  • ERR: 5
  • Table Small Diffs: 11
  • EIO: 9
  • ESO Small Diffs: 15
  • MTR Small Diffs: 14
  • Table Big Diffs: 3
  • Table String Diffs: 6
  • ESO Big Diffs: 5

@rraustad
Copy link
Collaborator

Just a sanity check on what happens with this branch. No real diff, operation changed because of convergence difference.

image

@github-actions
Copy link

⚠️ Regressions detected on ubuntu-24.04 for commit 14199e3

Regression Summary
  • ERR: 5
  • Table Small Diffs: 12
  • EIO: 11
  • ESO Small Diffs: 15
  • MTR Small Diffs: 14
  • Table Big Diffs: 2
  • Table String Diffs: 7
  • ESO Big Diffs: 5

@github-actions
Copy link

⚠️ Regressions detected on macos-14 for commit 14199e3

Regression Summary
  • ERR: 5
  • Table Small Diffs: 11
  • EIO: 9
  • ESO Small Diffs: 15
  • MTR Small Diffs: 14
  • Table Big Diffs: 3
  • Table String Diffs: 6
  • ESO Big Diffs: 5

@amirroth amirroth added Refactoring Includes code changes that don't change the functionality of the program, just perform refactoring NotIDDChange Code does not impact IDD (can be merged after IO freeze) and removed DoNotPublish Includes changes that shouldn't be reported in the changelog labels Feb 24, 2026
@amirroth
Copy link
Collaborator Author

Just a sanity check on what happens with this branch. No real diff, operation changed because of convergence difference.

image

Thanks @rraustad.

@github-actions
Copy link

⚠️ Regressions detected on macos-14 for commit 0fa27ec

Regression Summary
  • ERR: 5
  • Table Small Diffs: 11
  • EIO: 9
  • ESO Small Diffs: 15
  • MTR Small Diffs: 14
  • Table Big Diffs: 3
  • Table String Diffs: 6
  • ESO Big Diffs: 5

@github-actions
Copy link

⚠️ Regressions detected on ubuntu-24.04 for commit 0fa27ec

Regression Summary
  • ERR: 5
  • Table Small Diffs: 12
  • EIO: 11
  • ESO Small Diffs: 15
  • MTR Small Diffs: 14
  • Table Big Diffs: 2
  • Table String Diffs: 7
  • ESO Big Diffs: 5

@github-actions
Copy link

⚠️ Regressions detected on ubuntu-24.04 for commit 4f81891

Regression Summary
  • ERR: 5
  • Table Small Diffs: 12
  • EIO: 11
  • ESO Small Diffs: 15
  • MTR Small Diffs: 14
  • Table Big Diffs: 2
  • Table String Diffs: 7
  • ESO Big Diffs: 5

@github-actions
Copy link

⚠️ Regressions detected on macos-14 for commit 4f81891

Regression Summary
  • ERR: 5
  • Table Small Diffs: 11
  • EIO: 9
  • ESO Small Diffs: 15
  • MTR Small Diffs: 14
  • Table Big Diffs: 3
  • Table String Diffs: 6
  • ESO Big Diffs: 5

Copy link
Collaborator

@mitchute mitchute left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No major questions

Comment on lines +274 to +282
#ifdef GET_OUT
if (NIte > 20) {
assert(false);
Flag = NIte;
XRes = XTemp;
return;
}
#endif // GET_OUT

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this something you intend to leave in?

if (QTotLoad < QHeatFanOffMax - SmallLoad) {
// vary HW flow, leave air flow at minimum
ErrTolerance = this->ControllerOffset;
ErrTolerance = this->ControllerOffset / 2; // Added /2 to try to eliminate a "big" diff
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Clean this out?

auto f = [&state, H, RH, PB](Real64 const Tprov) { return H - Psychrometrics::PsyHFnTdbRhPb(state, Tprov, RH, PB); };

General::SolveRoot(state, Acc, MaxIte, SolFla, Tprov, f, T0, T1);
static General::SolveRootStats solveRootStats;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe here:

struct GeneralData : BaseGlobalStruct

Comment on lines +9289 to +9292
// Shared by all instances of this call to learn fastest algorithm. This is not
// thread, "object", or "state" safe, does it really need to be? Does it need to
// exist within state? Within each thermal tank object? That's probably excessive.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I can't think of why this wouldn't work. We wouldn't really expect different instances of the same coil to have different algorithms, right?


int constexpr MaxIte(500); // maximum number of iterations
Real64 constexpr Acc(0.001); // Accuracy of result from RegulaFalsi
Real64 constexpr Acc(0.005); // Accuracy of result from RegulaFalsi // was 0.001, trying to eliminate some "big" diffs
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems OK. (cleanup comment?)

if (PltSizNum > 0) {
DesignEntWaterTemp = state.dataSize->PlantSizData(PltSizNum).ExitTemp;
ratioTS = (DesignEntWaterTemp + state.dataWaterToAirHeatPumpSimple->CelsiustoKelvin) / Tref;
ratioTS = (DesignEntWaterTemp + Constant::Kelvin) / Tref;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's fun. How many layers of constexpr can we go here.

EXPECT_TRUE(WaterThermalTanks::TankOperatingMode::Floating == HeatPump.Mode); // expect HP to switch to floating mode since it reached set point
EXPECT_NEAR(58.0005664, Tank.TankTempAvg, 0.0000001); // average tank temp over time step
EXPECT_NEAR(58.0005664, Tank.SourceOutletTemp, 0.0000001); // source outlet = average tank temp
EXPECT_NEAR(58.0005664, Tank.TankTempAvg, 0.001); // Was 0.0000001 !?! // average tank temp over time step
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our temperature "sensors" are perfect, so yes ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

NotIDDChange Code does not impact IDD (can be merged after IO freeze) Refactoring Includes code changes that don't change the functionality of the program, just perform refactoring

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants